package quickSort;

import lock.ConcreteLock;
import lock.Lock;
import shared.Buffer;
import shared.MyComparable;
import shared.MyStop;
import shared.SortInput;
import shared.SortInputVisitor;

public class QuickSorter<T extends Comparable<? super T>> implements Runnable{

	private final Buffer<SortInput<T>> inputBuffer;
	private boolean started;
	private boolean done;
	private final Lock resultLock = new ConcreteLock(true);
	private final Buffer<T> outputBuffer;
	private final Buffer<SortInput<T>> firstSplitBuffer;
	private final Buffer<SortInput<T>> secondSplitBuffer;
	private QuickSorterState<T> state;
	
	public Lock getResultLock() {
		return resultLock;
	}

	public Buffer<SortInput<T>> getInputBuffer() {
		return inputBuffer;
	}

	public QuickSorter(final Buffer<SortInput<T>> inputBuffer) {
		this.inputBuffer = inputBuffer;
		this.outputBuffer = new Buffer<T>();
		this.firstSplitBuffer = new Buffer<SortInput<T>>();
		this.secondSplitBuffer = new Buffer<SortInput<T>>();
		this.state = new NoSorterStarted<T>();
	}

	public Buffer<T> getResult() {
		this.start();
		this.getResultLock().lock();
		return this.getOutputBuffer();
	}

	private void start() {
		if(!this.isStarted()){
			new Thread(this).start();
			this.setStarted(true);
		}
	}
	
	private Buffer<T> getOutputBuffer() {
		return this.outputBuffer;
	}

	public boolean isStarted() {
		return started;
	}

	public void setStarted(final boolean started) {
		this.started = started;
	}

	public boolean isDone() {
		return done;
	}

	public void setDone() {
		this.done = true;
		this.getResultLock().unlock();
	}

	@Override
	public void run() {
		final SortInput<T> median = this.getInputBuffer().get();
		median.accept(new SortInputVisitor<T>() {

			@Override
			public void visit(final MyComparable<T> myComparable) {
				QuickSorter.this.sort(myComparable);
			}

			@Override
			public void visit(final MyStop<T> myStop) {
				QuickSorter.this.setDone();
			}
		});
	}

	
	protected void sort(final MyComparable<T> median) {
		while(!this.isDone()){
			final SortInput<T> input = this.getInputBuffer().get();
			input.accept(new SortInputVisitor<T>() {

				@Override
				public void visit(final MyComparable<T> myComparable) {
					if(myComparable.getComparable().compareTo(median.getComparable()) < 0){
						QuickSorter.this.getFirstSplitBuffer().put(myComparable);
					} else {
						QuickSorter.this.getSecondSplitBuffer().put(myComparable);
					}
				}
				@Override
				public void visit(final MyStop<T> myStop) {
					final boolean firstSplitBufferNotEmpty = !QuickSorter.this.getFirstSplitBuffer().isEmpty();
					final boolean secondSplitBufferNotEmpty = !QuickSorter.this.getSecondSplitBuffer().isEmpty();
					
					if(firstSplitBufferNotEmpty){
						QuickSorter.this.getFirstSplitBuffer().put(myStop);
						final QuickSorter<T> firstQuickSorter = new QuickSorter<T>(QuickSorter.this.getFirstSplitBuffer());
						firstQuickSorter.start();
						QuickSorter.this.setState(new LessThanSorterStarted<T>(firstQuickSorter));
					}
					if(secondSplitBufferNotEmpty){
						QuickSorter.this.getSecondSplitBuffer().put(myStop);
						final QuickSorter<T> secondQuickSorter = new QuickSorter<T>(QuickSorter.this.getSecondSplitBuffer());
						secondQuickSorter.start();
						QuickSorter.this.getState().accept(new QuickSortStateVisitor<T>(){

							@Override
							public void visit(final NoSorterStarted<T> noSorterStarted) {
								QuickSorter.this.setState(new GreaterEqualsSorterStarted<T>(secondQuickSorter));
							}

							@Override
							public void visit(
									final LessThanSorterStarted<T> lessThanSorterStarted) {
								QuickSorter.this.setState(new BothSortersStarted<T>(lessThanSorterStarted.getLessThanSorter(),
										secondQuickSorter));
							}

							@Override
							public void visit(
									final GreaterEqualsSorterStarted<T> greaterEqualsSorterStarted) {}

							@Override
							public void visit(
									final BothSortersStarted<T> bothSortersStarted) {}
						
						});		
					}
					
					QuickSorter.this.getState().accept(new QuickSortStateVisitor<T>() {

						private void addResultToOutputBuffer(final QuickSorter<T> sorter){
							final Buffer<T> firstResultBuffer = sorter.getResult();
							while(!firstResultBuffer.isEmpty()){
								final T firstResult = firstResultBuffer.get();
								QuickSorter.this.getOutputBuffer().put(firstResult);
							}
						}
						private void addMedian(){
							QuickSorter.this.getOutputBuffer().put(median.getComparable());
						}
						@Override
						public void visit(final NoSorterStarted<T> noSorterStarted) {
							this.addMedian();
						}

						@Override
						public void visit(
								final LessThanSorterStarted<T> lessThanSorterStarted) {
							this.addResultToOutputBuffer(lessThanSorterStarted.getLessThanSorter());							
							this.addMedian();
						}

						@Override
						public void visit(
								final GreaterEqualsSorterStarted<T> greaterEqualsSorterStarted) {
							this.addMedian();
							this.addResultToOutputBuffer(greaterEqualsSorterStarted.getGreaterEqualSorter());
						}

						@Override
						public void visit(
								final BothSortersStarted<T> bothSortersStarted) {
							this.addResultToOutputBuffer(bothSortersStarted.getLessThanSorter());
							this.addMedian();
							this.addResultToOutputBuffer(bothSortersStarted.getGreaterEqualsSorter());
						}
					});
					QuickSorter.this.setDone();
				}
			});
		}
	}

	public Buffer<SortInput<T>> getFirstSplitBuffer() {
		return firstSplitBuffer;
	}

	public Buffer<SortInput<T>> getSecondSplitBuffer() {
		return secondSplitBuffer;
	}

	public QuickSorterState<T> getState() {
		return state;
	}

	public void setState(final QuickSorterState<T> state) {
		this.state = state;
	}
}
